import { computed, defineComponent, PropType, ref, setBlockTracking } from 'vue'
import {
  VisualEditorModelValue,
  VisualEditorConfig,
  VisualEditorComponent,
  createNewBlock,
  VisualEditorBlockData,
} from './visual-editor-type'
import { useVisualCommand } from './utils/visual.command'
import { useModel } from './utils/useModel'
import { VisualEditorBlock } from './visual-editor-block'
import { createEvent } from './plugins/event'
import './visual-editor.scss'

export const VisualEditor = defineComponent({
  props: {
    modelValue: {
      type: Object as PropType<VisualEditorModelValue>,
      required: true,
    },
    config: {
      type: Object as PropType<VisualEditorConfig>,
      required: true,
    },
  },
  emits: {
    'update:modelValue': (val?: VisualEditorModelValue) => true,
  },
  setup(props, ctx) {
    // 双向保定值容器中的组件数据
    const dataModel = useModel(
      () => props.modelValue,
      (val) => ctx.emit('update:modelValue', val)
    )
    // container 节点dom对象的引用
    const containerRef = ref({} as HTMLDivElement)
    // container节点的style样式对象
    const containerStyles = computed(() => ({
      width: `${dataModel.value.container.width}px`,
      height: `${dataModel.value.container.height}px`,
    }))
    // 计算选中与未选中的block数据
    const focusData = computed(() => {
      let focus: VisualEditorBlockData[] = []
      let unFocus: VisualEditorBlockData[] = []

      ~(dataModel.value.blocks || []).forEach((block) =>
        (block.focus ? focus : unFocus).push(block)
      )

      return {
        focus, // 设置选中数据
        unFocus, // 设置未选中数据
      }
    })

    const dragstart = createEvent()
    const dragend = createEvent()

    // 对外暴露的一些方法
    const methods = {
      clearFocus: (block?: VisualEditorBlockData) => {
        let blocks = dataModel.value.blocks || []
        if (blocks.length === 0) return
        if (!!block) {
          blocks = blocks.filter((item) => item !== block)
        }
        blocks.forEach((block) => (block.focus = false))
      },
      updateBlocks: (blocks: VisualEditorBlockData[]) => {
        dataModel.value = { ...dataModel.value, blocks }
      },
    }

    // 处理从菜单退拽组件到容器的相关操作
    const menuDraggier = (() => {
      let component = null as null | VisualEditorComponent

      const blockHandler = {
        /**
         * 处理拖拽菜单开始动作
         * @param e
         * @param current
         */
        dragStart: (e: DragEvent, current: VisualEditorComponent) => {
          containerRef.value.addEventListener(
            'dragenter',
            containerHandler.dragEnter
          )
          containerRef.value.addEventListener(
            'dragover',
            containerHandler.dragOver
          )
          containerRef.value.addEventListener(
            'dragleave',
            containerHandler.dragLeave
          )
          containerRef.value.addEventListener('drop', containerHandler.drop)
          component = current
          dragstart.emit()
        },
        /**
         * 处理菜单拖拽结束动作
         * @param e
         */
        dragEnd: (e: DragEvent) => {
          containerRef.value.removeEventListener(
            'dragenter',
            containerHandler.dragEnter
          )
          containerRef.value.removeEventListener(
            'dragover',
            containerHandler.dragOver
          )
          containerRef.value.removeEventListener(
            'dragleave',
            containerHandler.dragLeave
          )
          containerRef.value.removeEventListener('drop', containerHandler.drop)
          component = null
        },
      }

      const containerHandler = {
        /**
         * 拖拽菜单组件，进入容器的时候，设置鼠标为可放置状态
         * @param e
         */
        dragEnter: (e: DragEvent) => {
          e.dataTransfer!.dropEffect = 'move'
        },
        /**
         * 拖拽菜单组件，鼠标在容器中移动的时候，禁用默认事件
         * @param e
         */
        dragOver: (e: DragEvent) => {
          e.preventDefault()
        },
        /**
         * 如果拖拽过程中，鼠标离开了容器，设置鼠标为不可放置的状态
         * @param e
         */
        dragLeave: (e: DragEvent) => {
          e.dataTransfer!.dropEffect = 'none'
        },
        /**
         * 在子容器中放置的时候，设置事件对象的offsetX和offsetY添加一条组件数据
         * @param e
         */
        drop: (e: DragEvent) => {
          console.log('drop', component)
          const blocks = [...(dataModel.value.blocks || [])]
          blocks.push(
            createNewBlock({
              component: component!,
              top: e.offsetY,
              left: e.offsetX,
            })
          )
          methods.updateBlocks(blocks)
          dragend.emit()
        },
      }
      return blockHandler
    })()

    // 处理block选中的相关动作
    const focusHandler = (() => {
      return {
        container: {
          onMousedown: (e: MouseEvent) => {
            ~(dataModel.value.blocks || []).forEach(
              (block) => (block.focus = false)
            )
            e.stopPropagation()
            e.preventDefault()
            // 点击空白处，清空所有选中的block
            methods.clearFocus()
          },
        },
        block: {
          onMousedown: (e: MouseEvent, block: VisualEditorBlockData) => {
            e.stopPropagation()
            e.preventDefault()
            if (e.shiftKey) {
              // 如果按住了shift键，如果此时没有选中的block，就选中这个block，否则令这个block的选中状态取反
              if (focusData.value.focus.length <= 1) {
                block.focus = true
              } else {
                block.focus = !block.focus
              }
            } else {
              // 如果点击的这个block没有被选中，才清空 其他选中的block ，否则不做其它处理，放置拖拽多个block，取消其他block的选中状态
              if (!block.focus) {
                block.focus = true
                methods.clearFocus(block)
              }
            }
            blockDraggier.mousedown(e)
          },
        },
      }
    })()

    // 处理block在container中拖拽移动的相关动作
    const blockDraggier = (() => {
      let dragState = {
        startX: 0,
        startY: 0,
        startPos: [] as { left: number; top: number }[],
        dragging: false,
      }

      const mousedown = (e: MouseEvent) => {
        dragState = {
          startX: e.clientX,
          startY: e.clientY,
          startPos: focusData.value.focus.map(({ top, left }) => ({
            top,
            left,
          })),
          dragging: false,
        }

        document.addEventListener('mousemove', mousemove)
        document.addEventListener('mouseup', mouseup)
        dragstart.emit()
      }

      const mousemove = (e: MouseEvent) => {
        const durX = e.clientX - dragState.startX
        const durY = e.clientY - dragState.startY
        if (!dragState.dragging) {
          dragState.dragging = true
          dragstart.emit()
        }
        focusData.value.focus.forEach((block, index) => {
          block.top = dragState.startPos[index].top + durY
          block.left = dragState.startPos[index].left + durX
        })
      }

      const mouseup = () => {
        document.removeEventListener('mousemove', mousemove)
        document.removeEventListener('mouseup', mouseup)
      }

      return { mousedown }
    })()

    const commander = useVisualCommand({
      focusData,
      updateBlocks: methods.updateBlocks,
      dataModel,
      dragstart,
      dragend,
    })

    const buttons = [
      {
        label: '撤销',
        icon: 'icon-back',
        handler: commander.undo,
        tip: 'ctrl+z',
      },
      {
        label: '重做',
        icon: 'icon-forward',
        handler: commander.redo,
        tip: 'ctrl+y, ctrl+shift+z',
      },
      {
        label: '删除',
        icon: 'icon-delete',
        handler: () => commander.delete(),
        tip: 'ctrl+d, backspace, delete',
      },
    ]

    return () => (
      <div class="visual-editor">
        <div class="visual-editor-menu">
          {props.config.componentList.map((component) => (
            <div
              class="visual-editor-menu-item"
              draggable
              onDragend={menuDraggier.dragEnd}
              onDragstart={(e) => menuDraggier.dragStart(e, component)}
            >
              <span class="visual-editor-menu-item-label">
                {component.label}
              </span>
              {component.preview()}
            </div>
          ))}
        </div>
        <div class="visual-editor-header">
          {buttons.map((btn, index) => (
            <el-tooltip effect="dark" content={btn.tip} placement="bottom">
              <div
                key={index}
                class="visual-editor-head-button"
                onClick={btn.handler}
              >
                <i class={`iconfont ${btn.icon}`} />
                <span>{btn.label}</span>
              </div>
            </el-tooltip>
          ))}
        </div>
        <div class="visual-editor-operator">visual-editor-operator</div>
        <div class="visual-editor-body">
          <div class="visual-editor-content">
            <div
              class="visual-editor-container"
              style={containerStyles.value}
              ref={containerRef}
              {...focusHandler.container}
            >
              {dataModel.value.blocks &&
                dataModel.value.blocks.map((block, index) => (
                  <VisualEditorBlock
                    config={props.config}
                    block={block}
                    key={index}
                    {...{
                      onMousedown: (e: MouseEvent) =>
                        focusHandler.block.onMousedown(e, block),
                    }}
                  ></VisualEditorBlock>
                ))}
            </div>
          </div>
        </div>
      </div>
    )
  },
})
